/*
 *
 * Paros and its related class files.
 *
 * Paros is an HTTP/HTTPS proxy for assessing web application security.
 * Copyright (C) 2003-2004 Chinotec Technologies Company
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Clarified Artistic License
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Clarified Artistic License for more details.
 *
 * You should have received a copy of the Clarified Artistic License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
// ZAP: 2012/04/23 Removed unnecessary casts.
// ZAP: 2012/05/02 Changed to set the initial capacity of a List.
// ZAP: 2013/01/23 Clean up of exception handling/logging.
// ZAP: 2013/03/03 Issue 546: Remove all template Javadoc comments
// ZAP: 2013/05/02 Re-arranged all modifiers into Java coding standard order
// ZAP: 2015/08/19 Deprecated; Issue 1804: Disable processing of XML external entities by default
// ZAP: 2019/06/01 Normalise line endings.
// ZAP: 2019/06/05 Normalise format/style.
package org.parosproxy.paros.common;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.zaproxy.zap.utils.XmlUtils;

/** @deprecated (2.4.2) Use {@link org.zaproxy.zap.utils.ZapXmlConfiguration} instead. */
@Deprecated
public abstract class FileXML {

    private static final Logger logger = Logger.getLogger(FileXML.class);

    protected Document doc = null;
    protected DocumentBuilder docBuilder = null;
    protected DocumentBuilderFactory docBuilderFactory = null;

    public FileXML(String rootElementName) {

        String rootString = "<" + rootElementName + "></" + rootElementName + ">";
        try {
            docBuilderFactory = XmlUtils.newXxeDisabledDocumentBuilderFactory();
            docBuilder = docBuilderFactory.newDocumentBuilder();

            doc = docBuilder.parse(new InputSource(new StringReader(rootString)));

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    public Document getDocument() {
        return doc;
    }

    /*
     * Get a single element (first element) under a base element matching a tag
     */
    protected Element getElement(Element base, String childTag) {
        Element[] elements = getElements(base, childTag);
        if (elements == null) {
            return null;
        } else {
            return elements[0];
        }
    }

    protected Element getElement(String tag) {
        Element parent = doc.getDocumentElement();
        return getElement(parent, tag);
    }

    protected Element getElement(String[] path) {

        Element[] elements = getElements(path);
        if (elements == null) {
            return null;
        } else {
            return elements[0];
        }
    }

    /**
     * Get all elements under a base element matching a tag name
     *
     * @param base
     * @param childTag
     * @return
     */
    protected Element[] getElements(Element base, String childTag) {
        NodeList nodeList = base.getElementsByTagName(childTag);
        if (nodeList.getLength() == 0) {
            return null;
        }
        Element[] elements = new Element[nodeList.getLength()];
        for (int i = 0; i < nodeList.getLength(); i++) {
            elements[i] = (Element) nodeList.item(i);
        }
        return elements;
    }

    protected Element[] getElements(String tagName) {
        Element parent = doc.getDocumentElement();
        return getElements(parent, tagName);
    }

    protected Element[] getElements(String[] path) {
        NodeList nodeList = null;
        Element element = doc.getDocumentElement();
        for (int i = 0; i < path.length - 1; i++) {
            nodeList = element.getElementsByTagName(path[i]);
            if (nodeList.getLength() > 0) {
                element = (Element) nodeList.item(i);
            } else {
                return null;
            }
        }
        nodeList = element.getElementsByTagName(path[path.length - 1]);
        if (nodeList.getLength() == 0) {
            return null;
        }
        Element[] elements = new Element[nodeList.getLength()];
        for (int i = 0; i < nodeList.getLength(); i++) {
            elements[i] = (Element) nodeList.item(0);
        }
        return elements;
    }

    /**
     * Get the text in text node from the element.
     *
     * @param element Element to get text from
     * @return Text in the text node under the element
     */
    private String getText(Element element) {
        try {
            for (int i = 0; i < element.getChildNodes().getLength(); i++) {
                Node node = element.getChildNodes().item(i);
                if (node.getNodeType() == Node.TEXT_NODE) {
                    return node.getNodeValue();
                }
            }
        } catch (Exception e) {
        }
        return "";
    }

    /**
     * Get the value of the tag under a base element
     *
     * @param base
     * @param tag
     * @return
     */
    protected String getValue(Element base, String tag) {

        Element element = null;
        String result = "";
        try {
            // ZAP: Removed unnecessary cast.
            element = getElement(base, tag);
            result = getText(element);
        } catch (Exception e) {
        }
        return result;
    }

    protected String getValue(String tag) {
        Element element = doc.getDocumentElement();
        return getValue(element, tag);
    }

    protected List<String> getValues(String tag) {
        // ZAP: Removed unnecessary cast.
        NodeList nodeList = doc.getElementsByTagName(tag);
        // ZAP: Added variable "length".
        final int length = nodeList.getLength();
        // ZAP: Changed to set the initial capacity.
        ArrayList<String> resultList = new ArrayList<>(length);
        Element element = null;
        // ZAP: Changed to use the variable "length".
        for (int i = 0; i < length; i++) {
            element = (Element) nodeList.item(i);
            resultList.add(getText(element));
        }
        return resultList;
    }

    protected abstract void parse() throws Exception;

    public void readAndParseFile(String fileName) throws SAXException, IOException, Exception {
        readFile(fileName);
        parse();
    }

    protected void readFile(String fileName) throws SAXException, IOException {

        // xml document processing
        DocumentBuilderFactory factory = null;
        DocumentBuilder builder = null;

        try {
            factory = XmlUtils.newXxeDisabledDocumentBuilderFactory();
            factory.setValidating(false);
            builder = factory.newDocumentBuilder();
            // builder.setErrorHandler(new ErrorHandler() {
            // });
        } catch (ParserConfigurationException e) {
        }

        doc = builder.parse(fileName);
    }

    public void saveFile(String fileName) {
        //		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        // factory.setNamespaceAware(true);
        // factory.setValidating(true);
        File file = null;
        FileOutputStream outFile = null;

        try {
            file = new File(fileName);
            outFile = new FileOutputStream(file);
            // Use a Transformer for output
            TransformerFactory tFactory = TransformerFactory.newInstance();
            Transformer transformer = tFactory.newTransformer();
            DOMSource source = new DOMSource(doc);
            StreamResult result = new StreamResult(outFile);
            // StreamResult result = new StreamResult(System.out);

            transformer.transform(source, result);

        } catch (TransformerException | IOException e) {
            logger.error(e.getMessage(), e);
        } finally {
            if (outFile != null) {
                try {
                    outFile.close();
                } catch (IOException e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    public void setDocument(Document doc) throws Exception {
        this.doc = doc;
        parse();
    }

    protected void setValue(String tagName, String value) {
        Element element = null;
        try {
            // set only the first tag
            element = getElement(tagName);
            if (element == null) {
                // if not found, add to root element
                element = doc.createElement(tagName);
                doc.getDocumentElement().appendChild(element);
            }
            for (int i = 0; i < element.getChildNodes().getLength(); i++) {
                Node node = element.getChildNodes().item(i);
                if (node.getNodeType() == Node.TEXT_NODE) {
                    node.setNodeValue(value);
                    return;
                }
            }
            Node newNode = doc.createTextNode(value);
            element.appendChild(newNode);
        } catch (Exception e) {
        }
    }

    protected void setValue(String[] path, String value) {
        Element element = doc.getDocumentElement();
        NodeList nodeList = null;
        Element newElement = null;
        Node newNode = null;

        for (int i = 0; i < path.length; i++) {
            // ZAP: Removed unnecessary cast.
            nodeList = doc.getElementsByTagName(path[i]);
            if (nodeList.getLength() == 0) {
                // create element if not found
                newElement = doc.createElement(path[i]);
                element.appendChild(newElement);
                element = newElement;
            } else {
                // point to new element
                element = (Element) nodeList.item(0);
            }
        }

        // element located
        try {
            // search for text node and set value
            for (int i = 0; i < element.getChildNodes().getLength(); i++) {
                Node node = element.getChildNodes().item(i);
                if (node.getNodeType() == Node.TEXT_NODE) {
                    node.setNodeValue(value);
                    return;
                }
            }
            // if not found, create text node
            newNode = doc.createTextNode(value);
            element.appendChild(newNode);
        } catch (Exception e) {
        }
    }

    protected void removeElement(Element base, String tag) {
        Element[] elements = getElements(base, tag);
        if (elements == null) return;
        for (int i = 0; i < elements.length; i++) {
            try {
                base.removeChild(elements[i]);
            } catch (Exception e) {
            }
        }
    }

    protected void removeElement(String tag) {
        Element base = doc.getDocumentElement();
        removeElement(base, tag);
    }

    /**
     * @param base
     * @param tag
     * @param value
     * @return added element
     */
    protected Element addElement(Element base, String tag, String value) {
        Element element = doc.createElement(tag);
        base.appendChild(element);
        for (int i = 0; i < element.getChildNodes().getLength(); i++) {
            Node node = element.getChildNodes().item(i);
            if (node.getNodeType() == Node.TEXT_NODE) {
                node.setNodeValue(value);
                return element;
            }
        }
        Node newNode = doc.createTextNode(value);
        element.appendChild(newNode);
        return element;
    }

    protected Element addElement(Element base, String tag) {
        Element element = doc.createElement(tag);
        base.appendChild(element);
        return element;
    }

    protected Element addElement(String tag) {
        Element element = doc.createElement(tag);
        doc.getDocumentElement().appendChild(element);
        return element;
    }
}
